from urllib import request
from urllib import parse
from bs4 import BeautifulSoup  # 用于解析HTML
import multiprocessing  # 用于多进程


class BaiduRanking:  # 定义爬取排名的类
    def __init__(self, words, page_number, domain):
        self.words = words  # 查询的关键词列表
        self.page_number = page_number  # 查询的页面数量
        self.domain = domain  # 查询的域名
        self.opener = request.build_opener()  # 创建打开URL的工具对象
        self.opener.addheaders = [('User-agent',  # 添加模拟浏览器访问的header信息
                                   'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11')]
        self.position = []  # 域名在搜索引擎查询结果中出现的位置列表

    def get_urls(self, word):  # 定义每个关键词查询页面的URL列表生成器
        urls = []
        for pn in range(self.page_number):
            url = 'https://www.baidu.com/s?wd=%s&pn=%s' % (parse.quote_plus(word), pn * 10)  # 注意关键词转换格式
            urls.append(url)
        yield urls

    def get_result(self, url, position, current_page):  # 定义获取排名结果的方法
        print(url)
        try:
            html = self.opener.open(url).read().decode().replace('<b>', '').replace('</b>', '')  # 获取页面内容并替换掉字体加粗标签
            soup = BeautifulSoup(html, 'lxml')  # 解析HTML
            for i in range(current_page * 10 + 1, current_page * 10 + 11):  # 轮询查询结果的id
                try:
                    if self.domain in soup.find(id=i).find('a', 'c-showurl').string:  # 判断从查询结果中找到的网址字符串是否包含域名
                        position.append(i)  # 添加查询结果id到位置列表
                except:  # 发生异常时继续查询
                    continue
        except:
            pass  # 某页面发生异常时忽略

    def run_task(self):  # 定义运行爬取任务的方法
        for word in self.words:  # 遍历关键词列表
            position = multiprocessing.Manager().list()  # 创建多进程共享的列表变量
            current_page = 0  # 当前查询的页面编号
            print('正在查询关键词：%s' % word)
            for urls in self.get_urls(word):  # 遍历当前关键词需要爬取的所有URL
                processes = multiprocessing.Pool(5)  # 开启进程池
                for url in urls:  # 遍历当前关键词的所有URL
                    processes.apply_async(self.get_result, args=(url, position, current_page))  # 为每个URL爬取任务创建进程
                    current_page += 1  # 页面编号自增
                processes.close()  # 关闭进程池
                processes.join()  # 等待进程结束
            print('查询完毕。\n出现位置：%s' % sorted(position))  # 显示输出排序后的爬取结果


if __name__ == '__main__':
    domain = 'opython.com'  # 查询的域名
    words = ['专利新闻']  # 查询的关键词列表
    page_number = 10  # 每个关键词爬取的页面数量
    ranking = BaiduRanking(words=words, domain=domain, page_number=page_number)  # 实例化爬取排名的对象
    ranking.run_task()  # 运行爬取任务
